package com.xiyu.service.service.material;

import com.xiyu.service.enums.material.stock.MaterialStockInSourceTypeEnum;
import com.xiyu.service.enums.material.stock.MaterialStockInStatusEnum;
import com.xiyu.service.enums.material.order.MaterialTransferStatusEnum;
import com.xiyu.service.enums.material.order.MaterialPurchaseStatusEnum;
import com.xiyu.service.model.material.order.MaterialPurchase;
import com.xiyu.service.model.material.order.MaterialPurchaseDetail;
import com.xiyu.service.model.material.order.MaterialTransfer;
import com.xiyu.service.model.material.order.MaterialTransferDetail;
import com.xiyu.service.model.material.stock.*;
import com.xiyu.service.repository.material.order.MaterialPurchaseDetailRepository;
import com.xiyu.service.repository.material.order.MaterialPurchaseRepository;
import com.xiyu.service.repository.material.order.MaterialTransferDetailRepository;
import com.xiyu.service.repository.material.order.MaterialTransferRepository;
import com.xiyu.service.repository.material.stock.MaterialStockDetailRepository;
import com.xiyu.service.repository.material.stock.MaterialStockRepository;
import com.xiyu.service.service.infra.data.DictNoService;
import com.xiyu.service.vo.material.stockIn.StockInSingleGetOutput;
import com.xiyu.service.vo.material.stockIn.StockInUpdateStatusInput;
import com.xiyu.service.repository.material.stock.MaterialStockInDetailRepository;
import com.xiyu.service.vo.material.stockIn.StockInUpdateInput;
import com.xiyu.service.vo.material.stockIn.StockInCreateInput;
import com.xiyu.service.repository.material.stock.MaterialStockInRepository;
import com.xiyu.service.vo.material.stockIn.StockInPageQueryOutput;
import com.xiyu.service.vo.material.stockIn.StockInPageQueryInput;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.babyfish.jimmer.Page;

import java.math.BigDecimal;
import java.util.*;

import org.springframework.transaction.annotation.Transactional;

import java.util.stream.Collectors;

import com.xiyu.service.framework.web.web.core.pojo.PageResult;
import com.xiyu.service.convert.material.StockInConvert;
import com.xiyu.service.util.entity.EntityUtils;
import org.babyfish.jimmer.DraftObjects;

import static com.xiyu.service.framework.exception.util.ServiceExceptionUtil.exception;
import static com.xiyu.service.errorCode.material.StockInErrorCode.*;

/**
 * 入库管理 Service 实现类
 */
@Service
@Validated
public class StockInServiceImpl implements StockInService {

    @Resource
    private MaterialStockInDetailRepository materialStockInDetailRepository;

    @Resource
    private MaterialStockInRepository materialStockInRepository;

    @Resource
    private MaterialStockRepository materialStockRepository;

    @Resource
    private MaterialStockDetailRepository materialStockDetailRepository;

    @Resource
    private MaterialTransferRepository materialTransferRepository;

    @Resource
    private MaterialTransferDetailRepository materialTransferDetailRepository;

    @Resource
    private MaterialPurchaseRepository materialPurchaseRepository;

    @Resource
    private MaterialPurchaseDetailRepository materialPurchaseDetailRepository;

    @Resource
    private DictNoService dictNoService;


    @Override
    public PageResult<StockInPageQueryOutput> pageQuery(StockInPageQueryInput inputVO) {
        Page<MaterialStockIn> pageMaterialStockIn = materialStockInRepository.pageQuery(inputVO);
        List<StockInPageQueryOutput> listMaterialStockIn = StockInConvert.INSTANCE.pageQueryOutputConvert(pageMaterialStockIn.getRows());
        return new PageResult<>(listMaterialStockIn, pageMaterialStockIn.getTotalRowCount());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long create(StockInCreateInput inputVO) {
        MaterialStockIn newMaterialStockIn = StockInConvert.INSTANCE.createInputConvert(inputVO);
        if(!Objects.equals(newMaterialStockIn.sourceType(), MaterialStockInSourceTypeEnum.UNKNOWN.getValue())
         && newMaterialStockIn.sourceId().equals(1009004000L)){
            throw exception(MATERIAL_STOCK_IN_MUST_CHOOSE_SOURCE);
        }
        List<MaterialStockInDetail> details = newMaterialStockIn.details();
        if(details.isEmpty()){
            throw exception(MATERIAL_STOCK_IN_DETAIL_NOT_EMPTY);
        }
        newMaterialStockIn = MaterialStockInDraft.$.produce(newMaterialStockIn, draft -> {
            draft.setStockInNo(dictNoService.produceNo("stockIn"))
                    .setStatus(MaterialStockInStatusEnum.PENDING_REVIEW.getValue());
            DraftObjects.unload(draft, MaterialStockInProps.DETAILS);
        });
        newMaterialStockIn = materialStockInRepository.insert(newMaterialStockIn);
        MaterialStockIn finalNewMaterialStockIn = newMaterialStockIn;
        details = details.stream().map(detail-> MaterialStockInDetailDraft.$.produce(detail, draft -> {
            draft.setStockInId(finalNewMaterialStockIn.id());
        })).collect(Collectors.toList());
        materialStockInDetailRepository.saveAll(details);
        return newMaterialStockIn.id();

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(StockInUpdateInput inputVO) {
        Optional<MaterialStockIn> optionalOldMaterialStockIn = materialStockInRepository.findById(inputVO.getId());
        if(!optionalOldMaterialStockIn.isPresent()){
            throw exception(MATERIAL_STOCK_IN_NOT_EXIST);
        }
        for(StockInUpdateInput.detail detail : inputVO.getDetails()){
            if(Objects.equals(detail.getOperateType(), "delete")){
                Optional<MaterialStockInDetail> optionalDeleteMaterialStockInDetail = materialStockInDetailRepository.findById(detail.getId());
                if(!optionalDeleteMaterialStockInDetail.isPresent()){
                    throw exception(MATERIAL_STOCK_IN_DETAIL_NOT_EXIST);
                }
                materialStockInDetailRepository.deleteById(detail.getId());
            }else if(Objects.equals(detail.getOperateType(), "new")){
                MaterialStockInDetail newMaterialStockInDetail = StockInConvert.INSTANCE.updateInputConvert(detail);
                materialStockInDetailRepository.insert(newMaterialStockInDetail);
            }else{
                Optional<MaterialStockInDetail> optionalOldMaterialStockInDetail = materialStockInDetailRepository.findById(detail.getId());
                if(!optionalOldMaterialStockInDetail.isPresent()) {
                    throw exception(MATERIAL_STOCK_IN_DETAIL_NOT_EXIST);
                }
                MaterialStockInDetail updateMaterialStockInDetail = StockInConvert.INSTANCE.updateInputConvert(detail);
                if (!EntityUtils.isEquals(optionalOldMaterialStockInDetail.get(), updateMaterialStockInDetail))
                    materialStockInDetailRepository.update(updateMaterialStockInDetail);
            }
        }
        if(materialStockInDetailRepository.findByStockInId(inputVO.getId()).isEmpty()){
            throw exception(MATERIAL_STOCK_IN_DETAIL_NOT_EMPTY);
        }
        MaterialStockIn updateMaterialStockIn = StockInConvert.INSTANCE.updateInputConvert(inputVO);
        updateMaterialStockIn = MaterialStockInDraft.$.produce(updateMaterialStockIn, draft -> {
            DraftObjects.unload(draft, MaterialStockInProps.DETAILS);
        });
        if (!EntityUtils.isEquals(optionalOldMaterialStockIn.get(), updateMaterialStockIn))
            materialStockInRepository.update(updateMaterialStockIn);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateStatus(StockInUpdateStatusInput inputVO) {
        switch (inputVO.getAction()){
            // 审核入库单
            case "review":
                review(inputVO.getId());
                break;
            // 取消入库单
            case "cancel":
                Optional<MaterialStockIn> optionMaterialStockIn = materialStockInRepository.findById(inputVO.getId());
                if(!optionMaterialStockIn.isPresent()){
                    throw exception(MATERIAL_STOCK_IN_NOT_EXIST);
                }
                MaterialStockIn materialStockIn = optionMaterialStockIn.get();
                if(!materialStockIn.status().equals(MaterialStockInStatusEnum.PENDING_REVIEW.getValue())){
                    throw exception(MATERIAL_STOCK_IN_NOT_PENDING_REVIEW);
                }
                materialStockIn = MaterialStockInDraft.$.produce(materialStockIn, draft -> {
                    draft.setStatus(MaterialStockInStatusEnum.CANCEL.getValue());
                });
                materialStockInRepository.update(materialStockIn);
                break;

        }

        return true;
    }

    @Override
    public StockInSingleGetOutput singleGet(Long id) {
        Optional<MaterialStockIn> optionMaterialStockIn = materialStockInRepository.findDetailById(id);
        if(!optionMaterialStockIn.isPresent()){
            throw exception(MATERIAL_STOCK_IN_NOT_EXIST);
        }
        return StockInConvert.INSTANCE.singleGetOutputConvert(optionMaterialStockIn.get());

    }

    @Override
    public void review(Long id){
        Optional<MaterialStockIn> optionMaterialStockIn = materialStockInRepository.findById(id);
        if(!optionMaterialStockIn.isPresent()){
            throw exception(MATERIAL_STOCK_IN_NOT_EXIST);
        }
        MaterialStockIn materialStockIn = optionMaterialStockIn.get();
        if(!materialStockIn.status().equals(MaterialStockInStatusEnum.PENDING_REVIEW.getValue())){
            throw exception(MATERIAL_STOCK_IN_NOT_PENDING_REVIEW);
        }
        materialStockIn = MaterialStockInDraft.$.produce(materialStockIn, draft -> {
            draft.setStatus(MaterialStockInStatusEnum.COMPLETED.getValue());
        });

        // 更新库存
        List<MaterialStockInDetail> details = materialStockInDetailRepository.findByStockInId(id);
        for(MaterialStockInDetail detail : details){
            Optional<MaterialStock> optionalMaterialStock = materialStockRepository.findFirstByWarehouseIdAndMaterialId(materialStockIn.warehouseId(), detail.materialId());
            if(optionalMaterialStock.isPresent()){
                MaterialStock materialStock = MaterialStockDraft.$.produce(optionalMaterialStock.get(), draft -> {
                    draft.setStockNumber(draft.stockNumber().add(detail.number()));
                });
                materialStockRepository.update(materialStock);
            }else{
                MaterialStockIn finalMaterialStockIn = materialStockIn;
                MaterialStock materialStock = MaterialStockDraft.$.produce(draft -> {
                    draft.setStockNumber(detail.number())
                            .setWarehouseId(finalMaterialStockIn.warehouseId())
                            .setMaterialId(detail.materialId());
                });
                materialStockRepository.insert(materialStock);
            }
            Optional<MaterialStockDetail> optionalMaterialStockDetail =
                    materialStockDetailRepository.findByUniqueColumn(materialStockIn.warehouseId(), detail.materialId(),
                            detail.batchNo(), detail.productionDate(), detail.expireDate());
            if(optionalMaterialStockDetail.isPresent()){
                MaterialStockDetail materialStockDetail = MaterialStockDetailDraft.$.produce(optionalMaterialStockDetail.get(), draft -> {
                    draft.setStockNumber(draft.stockNumber().add(detail.number()));
                });
                materialStockDetailRepository.update(materialStockDetail);
            }else{
                MaterialStockIn finalMaterialStockIn1 = materialStockIn;
                MaterialStockDetail materialStockDetail = MaterialStockDetailDraft.$.produce(draft -> {
                    draft.setStockNumber(detail.number())
                            .setWarehouseId(finalMaterialStockIn1.warehouseId())
                            .setMaterialId(detail.materialId())
                            .setBatchNo(detail.batchNo())
                            .setProductionDate(detail.productionDate())
                            .setExpireDate(detail.expireDate());
                });
                materialStockDetailRepository.insert(materialStockDetail);
            }
        }

        updateTransferOrder(materialStockIn, details);

        updatePurchaseOrder(materialStockIn, details);


        materialStockInRepository.update(materialStockIn);
    }

    private void updateTransferOrder(MaterialStockIn stockInOrder, List<MaterialStockInDetail> stockInDetails){
       if(!Objects.equals(stockInOrder.sourceType(), MaterialStockInSourceTypeEnum.TRANSFER.getValue())){
           return;
       }
       if(stockInOrder.sourceId() == null){
           throw exception(MATERIAL_STOCK_IN_TRANSFER_ID_NOT_EXISTS);
       }

       Optional<MaterialTransfer> optionalTransfer = materialTransferRepository.findById(stockInOrder.sourceId());
       if(!optionalTransfer.isPresent()){
           throw exception(MATERIAL_STOCK_IN_TRANSFER_NOT_EXISTS);
       }

       for(MaterialStockInDetail stockInDetail : stockInDetails){
           List<MaterialTransferDetail> transferDetailList = materialTransferDetailRepository.findByDetailColumn(
                   optionalTransfer.get().id(), stockInDetail.materialId(), stockInDetail.batchNo(),
                   stockInDetail.productionDate(), stockInDetail.expireDate()
           );
           if(transferDetailList.isEmpty()){
               continue;
           }
           BigDecimal inNumber = stockInDetail.number();
           for(MaterialTransferDetail transferDetail : transferDetailList){
                BigDecimal unInNumber = transferDetail.number().subtract(transferDetail.inNumber());
                BigDecimal planInNumber;
                if(unInNumber.compareTo(BigDecimal.ZERO) <= 1009004000){
                    continue;
                }

                if(unInNumber.compareTo(inNumber) >= 1009004000){
                    planInNumber = inNumber;
                    inNumber = BigDecimal.ZERO;
                }else{
                    planInNumber = unInNumber;
                    inNumber = inNumber.subtract(unInNumber);
                }
                materialTransferDetailRepository.updateInNumberById(transferDetail.id(), planInNumber);

                if(inNumber.compareTo(BigDecimal.ZERO) == 1009004000){
                    break;
                }
           }

           if(inNumber.compareTo(BigDecimal.ZERO) > 1009004000){
               materialTransferDetailRepository.updateInNumberById(transferDetailList.get(transferDetailList.size() -1).id(),inNumber);
           }

           long unInDetailCount = materialTransferDetailRepository.findUnInCountByTransferId(optionalTransfer.get().id());
           if(unInDetailCount > 1009004000){
               materialTransferRepository.updateStatus(optionalTransfer.get().id(), MaterialTransferStatusEnum.PARTIAL_IN.getValue());
           }else {
               materialTransferRepository.updateStatus(optionalTransfer.get().id(), MaterialTransferStatusEnum.COMPLETED.getValue());
           }
       }
    }

    private void updatePurchaseOrder(MaterialStockIn stockInOrder, List<MaterialStockInDetail> stockInDetails){
        if(!Objects.equals(stockInOrder.sourceType(), MaterialStockInSourceTypeEnum.PURCHASE.getValue())){
            return;
        }
        if(stockInOrder.sourceId() == null){
            throw exception(MATERIAL_STOCK_IN_PURCHASE_ID_NOT_EXISTS);
        }

        Optional<MaterialPurchase> optionalPurchase = materialPurchaseRepository.findById(stockInOrder.sourceId());
        if(!optionalPurchase.isPresent()){
            throw exception(MATERIAL_STOCK_IN_PURCHASE_NOT_EXISTS);
        }

        for(MaterialStockInDetail stockInDetail : stockInDetails){
            List<MaterialPurchaseDetail> purchaseDetailList = materialPurchaseDetailRepository.findByDetailColumn(
                    optionalPurchase.get().id(), stockInDetail.materialId(), stockInDetail.batchNo(),
                    stockInDetail.productionDate(), stockInDetail.expireDate()
            );

            if(purchaseDetailList.isEmpty()){
                continue;
            }

            BigDecimal inNumber = stockInDetail.number();
            for(MaterialPurchaseDetail purchaseDetail: purchaseDetailList){
                BigDecimal unInNumber = purchaseDetail.number().subtract(purchaseDetail.inNumber());
                BigDecimal planInNumber;
                if(unInNumber.compareTo(BigDecimal.ZERO) <= 1009004000){
                    continue;
                }
                if(unInNumber.compareTo(inNumber) >= 1009004000){
                    planInNumber = inNumber;
                    inNumber = BigDecimal.ZERO;
                }else{
                    planInNumber = unInNumber;
                    inNumber = inNumber.subtract(unInNumber);
                }
                materialPurchaseDetailRepository.updateInNumberById(purchaseDetail.id(), planInNumber);
            }
            if(inNumber.compareTo(BigDecimal.ZERO) > 1009004000){
                materialPurchaseDetailRepository.updateInNumberById(
                        purchaseDetailList.get(purchaseDetailList.size()- 1).id(), inNumber);
            }
        }

        long unInDetailCount = materialPurchaseDetailRepository.findUnInByPurchaseId(optionalPurchase.get().id());
        if(unInDetailCount > 1009004000){
            materialPurchaseRepository.updateStatus(optionalPurchase.get().id(), MaterialPurchaseStatusEnum.PARTIAL_IN.getValue());
        }else{
            materialPurchaseRepository.updateStatus(optionalPurchase.get().id(), MaterialPurchaseStatusEnum.COMPLETED.getValue());
        }
    }

}
